﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Interpreter.BuiltIns
{
	internal class SystemMath : Module
	{
		public override void Register(FunctionRegistry registry)
		{
			registry.Register(8, new SystemMathSin());
			registry.Register(9, new SystemMathCos());
			registry.Register(10, new SystemMathTan());
			registry.Register(15, new SystemMathPow());
			registry.Register(43, new SystemMathParseInt());
			registry.Register(44, new SystemMathTryParseInt());
			registry.Register(48, new SystemMathFloor());
			registry.Register(49, new SystemMathCeiling());
		}

		private class SystemMathPow : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value baseNum = stackContext.Args[0];
				Value power = stackContext.Args[1];

				if (baseNum.Type == Value.TYPE_INTEGER && power.Type == Value.TYPE_INTEGER)
				{
					int a = (int)baseNum.Value0;
					int b = (int)power.Value0;
					if (a == 0 && b == 0)
					{
						return new Value(Value.TYPE_STRING, "TODO: throw indeterminate exception");
					}

					return Value.GetInteger((int)System.Math.Pow(a, b));
				}
				else
				{
					double a = baseNum.Type == Value.TYPE_INTEGER ? ((double)(int)baseNum.Value0) : ((double)baseNum.Value0);
					double b = baseNum.Type == Value.TYPE_INTEGER ? ((double)(int)baseNum.Value0) : ((double)baseNum.Value0);

					if (a == 0 && b == 0)
					{
						return new Value(Value.TYPE_STRING, "TODO: throw indeterminate exception");
					}

					// TODO: check for complex numbers
					return new Value(Value.TYPE_FLOAT, System.Math.Pow(a, b));
				}
			}
		}

		private class SystemMathFloor : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value v = stackContext.Args[0];
				return v.Type == Value.TYPE_FLOAT
					? Value.GetInteger((int)(double)v.Value0)
					: v;
			}
		}

		private class SystemMathCeiling : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value v = stackContext.Args[0];
				return v.Type == Value.TYPE_FLOAT
					? Value.GetInteger((int)System.Math.Ceiling((double)v.Value0))
					: v;
			}
		}

		private class SystemMathParseInt : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				int output;
				if (int.TryParse(stackContext.Args[0].Value0.ToString(), out output))
				{
					return Value.GetInteger(output);
				}
				throw new NotImplementedException("TODO: throw an exception");
			}
		}

		private class SystemMathTryParseInt : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				int output;
				if (int.TryParse(stackContext.Args[0].Value0.ToString(), out output))
				{
					return Value.GetInteger(output);
				}
				return args[1];
			}
		}

		private class SystemMathCos : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value value = stackContext.Args[0];
				double doubleValue = value.Type == Value.TYPE_INTEGER ? ((double)(int)value.Value0) : (double)value.Value0;
				return new Value(Value.TYPE_FLOAT, System.Math.Cos(doubleValue));
			}
		}

		private class SystemMathSin : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value value = stackContext.Args[0];
				double doubleValue = value.Type == Value.TYPE_INTEGER ? ((double)(int)value.Value0) : (double)value.Value0;
				return new Value(Value.TYPE_FLOAT, System.Math.Sin(doubleValue));
			}
		}

		private class SystemMathTan : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				// TODO: if cosine of doubleValue is 0, throw a division by 0 exception
				Value value = stackContext.Args[0];
				double doubleValue = value.Type == Value.TYPE_INTEGER ? ((double)(int)value.Value0) : (double)value.Value0;
				return new Value(Value.TYPE_FLOAT, System.Math.Tan(doubleValue));
			}
		}
	}
}
